/**
*  \file md5.c
*
*  \brief Message-Digest Algorithm 5 (MD5)
*
*  This module calculates the MD5 checksum, a 128-bit hash value, of any message according to RFC1321.
*  To use this module it is necessary to provide a state object of the structure 'sMD5_state_t'.
*  The result (message digest) will be returned as four 32-bit words using the little endian convention.
*  The message digest starts with the least significant byte of the first hash word and ends with the most
*  significant byte of the last hash word.
*
*  \author D'Aprea F.  <francesco.daprea@linde-mh.de>
*
*/

/*===============================================[ private includes  ]================================================*/
#include "md5.h"

#include <string.h>
#include "../binarith/binarith.h"
#include "../cryptutil/cryptutil.h"

/*===============================================[ private defines   ]================================================*/
/*
* Sine constants according to RFC1321.
*
* Python:
* for l in range(0,64):
*       x = math.floor( abs(math.sin(l+1)) * pow(2,32) )
*       print "#define T" + "%0.2d" % (l+1) + " " + "0x%0.8X" % x
*
*/
#define MD5_T01 0xD76AA478UL /**< Sine constant according to RFC1321. */
#define MD5_T02 0xE8C7B756UL /**< Sine constant according to RFC1321. */
#define MD5_T03 0x242070DBUL /**< Sine constant according to RFC1321. */
#define MD5_T04 0xC1BDCEEEUL /**< Sine constant according to RFC1321. */
#define MD5_T05 0xF57C0FAFUL /**< Sine constant according to RFC1321. */
#define MD5_T06 0x4787C62AUL /**< Sine constant according to RFC1321. */
#define MD5_T07 0xA8304613UL /**< Sine constant according to RFC1321. */
#define MD5_T08 0xFD469501UL /**< Sine constant according to RFC1321. */
#define MD5_T09 0x698098D8UL /**< Sine constant according to RFC1321. */
#define MD5_T10 0x8B44F7AFUL /**< Sine constant according to RFC1321. */
#define MD5_T11 0xFFFF5BB1UL /**< Sine constant according to RFC1321. */
#define MD5_T12 0x895CD7BEUL /**< Sine constant according to RFC1321. */
#define MD5_T13 0x6B901122UL /**< Sine constant according to RFC1321. */
#define MD5_T14 0xFD987193UL /**< Sine constant according to RFC1321. */
#define MD5_T15 0xA679438EUL /**< Sine constant according to RFC1321. */
#define MD5_T16 0x49B40821UL /**< Sine constant according to RFC1321. */
#define MD5_T17 0xF61E2562UL /**< Sine constant according to RFC1321. */
#define MD5_T18 0xC040B340UL /**< Sine constant according to RFC1321. */
#define MD5_T19 0x265E5A51UL /**< Sine constant according to RFC1321. */
#define MD5_T20 0xE9B6C7AAUL /**< Sine constant according to RFC1321. */
#define MD5_T21 0xD62F105DUL /**< Sine constant according to RFC1321. */
#define MD5_T22 0x02441453UL /**< Sine constant according to RFC1321. */
#define MD5_T23 0xD8A1E681UL /**< Sine constant according to RFC1321. */
#define MD5_T24 0xE7D3FBC8UL /**< Sine constant according to RFC1321. */
#define MD5_T25 0x21E1CDE6UL /**< Sine constant according to RFC1321. */
#define MD5_T26 0xC33707D6UL /**< Sine constant according to RFC1321. */
#define MD5_T27 0xF4D50D87UL /**< Sine constant according to RFC1321. */
#define MD5_T28 0x455A14EDUL /**< Sine constant according to RFC1321. */
#define MD5_T29 0xA9E3E905UL /**< Sine constant according to RFC1321. */
#define MD5_T30 0xFCEFA3F8UL /**< Sine constant according to RFC1321. */
#define MD5_T31 0x676F02D9UL /**< Sine constant according to RFC1321. */
#define MD5_T32 0x8D2A4C8AUL /**< Sine constant according to RFC1321. */
#define MD5_T33 0xFFFA3942UL /**< Sine constant according to RFC1321. */
#define MD5_T34 0x8771F681UL /**< Sine constant according to RFC1321. */
#define MD5_T35 0x6D9D6122UL /**< Sine constant according to RFC1321. */
#define MD5_T36 0xFDE5380CUL /**< Sine constant according to RFC1321. */
#define MD5_T37 0xA4BEEA44UL /**< Sine constant according to RFC1321. */
#define MD5_T38 0x4BDECFA9UL /**< Sine constant according to RFC1321. */
#define MD5_T39 0xF6BB4B60UL /**< Sine constant according to RFC1321. */
#define MD5_T40 0xBEBFBC70UL /**< Sine constant according to RFC1321. */
#define MD5_T41 0x289B7EC6UL /**< Sine constant according to RFC1321. */
#define MD5_T42 0xEAA127FAUL /**< Sine constant according to RFC1321. */
#define MD5_T43 0xD4EF3085UL /**< Sine constant according to RFC1321. */
#define MD5_T44 0x04881D05UL /**< Sine constant according to RFC1321. */
#define MD5_T45 0xD9D4D039UL /**< Sine constant according to RFC1321. */
#define MD5_T46 0xE6DB99E5UL /**< Sine constant according to RFC1321. */
#define MD5_T47 0x1FA27CF8UL /**< Sine constant according to RFC1321. */
#define MD5_T48 0xC4AC5665UL /**< Sine constant according to RFC1321. */
#define MD5_T49 0xF4292244UL /**< Sine constant according to RFC1321. */
#define MD5_T50 0x432AFF97UL /**< Sine constant according to RFC1321. */
#define MD5_T51 0xAB9423A7UL /**< Sine constant according to RFC1321. */
#define MD5_T52 0xFC93A039UL /**< Sine constant according to RFC1321. */
#define MD5_T53 0x655B59C3UL /**< Sine constant according to RFC1321. */
#define MD5_T54 0x8F0CCC92UL /**< Sine constant according to RFC1321. */
#define MD5_T55 0xFFEFF47DUL /**< Sine constant according to RFC1321. */
#define MD5_T56 0x85845DD1UL /**< Sine constant according to RFC1321. */
#define MD5_T57 0x6FA87E4FUL /**< Sine constant according to RFC1321. */
#define MD5_T58 0xFE2CE6E0UL /**< Sine constant according to RFC1321. */
#define MD5_T59 0xA3014314UL /**< Sine constant according to RFC1321. */
#define MD5_T60 0x4E0811A1UL /**< Sine constant according to RFC1321. */
#define MD5_T61 0xF7537E82UL /**< Sine constant according to RFC1321. */
#define MD5_T62 0xBD3AF235UL /**< Sine constant according to RFC1321. */
#define MD5_T63 0x2AD7D2BBUL /**< Sine constant according to RFC1321. */
#define MD5_T64 0xEB86D391UL /**< Sine constant according to RFC1321. */

/*
* Left shift constants according to RFC1321.
*/
#define MD5_S11  7U /**< Left shift constant according to RFC1321. */
#define MD5_S12 12U /**< Left shift constant according to RFC1321. */
#define MD5_S13 17U /**< Left shift constant according to RFC1321. */
#define MD5_S14 22U /**< Left shift constant according to RFC1321. */
#define MD5_S21  5U /**< Left shift constant according to RFC1321. */
#define MD5_S22  9U /**< Left shift constant according to RFC1321. */
#define MD5_S23 14U /**< Left shift constant according to RFC1321. */
#define MD5_S24 20U /**< Left shift constant according to RFC1321. */
#define MD5_S31  4U /**< Left shift constant according to RFC1321. */
#define MD5_S32 11U /**< Left shift constant according to RFC1321. */
#define MD5_S33 16U /**< Left shift constant according to RFC1321. */
#define MD5_S34 23U /**< Left shift constant according to RFC1321. */
#define MD5_S41  6U /**< Left shift constant according to RFC1321. */
#define MD5_S42 10U /**< Left shift constant according to RFC1321. */
#define MD5_S43 15U /**< Left shift constant according to RFC1321. */
#define MD5_S44 21U /**< Left shift constant according to RFC1321. */

/*===============================================[ private datatypes ]================================================*/

/*===============================================[ private variables ]================================================*/

/*===============================================[ public variables  ]================================================*/

/*===============================================[ private functions ]================================================*/

/**
* Basic logical function F according to RFC1321.
*/
STATIC_INLINE U32 md5_F( const U32 b, const U32 c, const U32 d )
{
   return ( d ^ ( b & ( c ^ d ) ) ); /* optimized, instead of ( ( b & c ) | ( (~b) & d ) ) */
}

/**
* Basic logical function G according to RFC1321.
*/
STATIC_INLINE U32 md5_G( const U32 b, const U32 c, const U32 d )
{
   return ( c ^ ( d & ( b ^ c ) ) ); /* optimized, instead of ( ( b & d ) | ( c & (~d) )   */
}

/**
* Basic logical functions H according to RFC1321.
*/
STATIC_INLINE U32 md5_H( const U32 b, const U32 c, const U32 d )
{
   return  ( b ^ c  ^ d );
}

/**
* Basic logical functions I according to RFC1321.
*/
STATIC_INLINE U32 md5_I( const U32 b, const U32 c, const U32 d )
{
   return ( c ^ ( b | (~d) ) );
}

/**
* Single step for 1st round using basic function F.
*/
STATIC_INLINE U32 md5_FF( const U32 a
                        , const U32 b
                        , const U32 c
                        , const U32 d
                        , const U32 word
                        , const U32 sine_const
                        , const U8  shift_const)
{
   return b + bin_rotate_left_U32( ( a + md5_F( b, c, d ) + word + sine_const ) , shift_const );
}
/**
* Single step for 2nd round using basic function G.
*/
STATIC_INLINE U32 md5_GG( const U32 a
                        , const U32 b
                        , const U32 c
                        , const U32 d
                        , const U32 word
                        , const U32 sine_const
                        , const U8  shift_const)
{
   return b + bin_rotate_left_U32( ( a + md5_G( b, c, d ) + word + sine_const ) , shift_const );
}
/**
* Single step for 3rd round using basic function H.
*/
STATIC_INLINE U32 md5_HH( const U32 a
                        , const U32 b
                        , const U32 c
                        , const U32 d
                        , const U32 word
                        , const U32 sine_const
                        , const U8  shift_const)
{
   return b + bin_rotate_left_U32( ( a + md5_H( b, c, d ) + word + sine_const ) , shift_const );
}
/**
* Single step for 4th round using basic function I.
*/
STATIC_INLINE U32 md5_II( const U32 a
                        , const U32 b
                        , const U32 c
                        , const U32 d
                        , const U32 word
                        , const U32 sine_const
                        , const U8  shift_const)
{
   return b + bin_rotate_left_U32( ( a + md5_I( b, c, d ) + word + sine_const ) , shift_const );
}

/**
* This function calculates the hash of a complete 64-byte message packets.
*
* \param arg_ptr_data Pointer to message packet (16 * 32 bits = 512 bits).
* \param arg_ptr_hash Pointer to an array with 4 32-bit values.
*/
STATIC_INLINE void md5_calc_packet_hash(U32 * arg_ptr_data,U32 * arg_ptr_hash )
{
   U32 loc_a = *   arg_ptr_hash ;
   U32 loc_b = *(++arg_ptr_hash);
   U32 loc_c = *(++arg_ptr_hash);
   U32 loc_d = *(++arg_ptr_hash);

   /* 1st round */
   loc_a = md5_FF( loc_a, loc_b, loc_c, loc_d, *   arg_ptr_data , MD5_T01, MD5_S11 );
   loc_d = md5_FF( loc_d, loc_a, loc_b, loc_c, *(++arg_ptr_data), MD5_T02, MD5_S12 );
   loc_c = md5_FF( loc_c, loc_d, loc_a, loc_b, *(++arg_ptr_data), MD5_T03, MD5_S13 );
   loc_b = md5_FF( loc_b, loc_c, loc_d, loc_a, *(++arg_ptr_data), MD5_T04, MD5_S14 );

   loc_a = md5_FF( loc_a, loc_b, loc_c, loc_d, *(++arg_ptr_data), MD5_T05, MD5_S11 );
   loc_d = md5_FF( loc_d, loc_a, loc_b, loc_c, *(++arg_ptr_data), MD5_T06, MD5_S12 );
   loc_c = md5_FF( loc_c, loc_d, loc_a, loc_b, *(++arg_ptr_data), MD5_T07, MD5_S13 );
   loc_b = md5_FF( loc_b, loc_c, loc_d, loc_a, *(++arg_ptr_data), MD5_T08, MD5_S14 );

   loc_a = md5_FF( loc_a, loc_b, loc_c, loc_d, *(++arg_ptr_data), MD5_T09, MD5_S11 );
   loc_d = md5_FF( loc_d, loc_a, loc_b, loc_c, *(++arg_ptr_data), MD5_T10, MD5_S12 );
   loc_c = md5_FF( loc_c, loc_d, loc_a, loc_b, *(++arg_ptr_data), MD5_T11, MD5_S13 );
   loc_b = md5_FF( loc_b, loc_c, loc_d, loc_a, *(++arg_ptr_data), MD5_T12, MD5_S14 );

   loc_a = md5_FF( loc_a, loc_b, loc_c, loc_d, *(++arg_ptr_data), MD5_T13, MD5_S11 );
   loc_d = md5_FF( loc_d, loc_a, loc_b, loc_c, *(++arg_ptr_data), MD5_T14, MD5_S12 );
   loc_c = md5_FF( loc_c, loc_d, loc_a, loc_b, *(++arg_ptr_data), MD5_T15, MD5_S13 );
   loc_b = md5_FF( loc_b, loc_c, loc_d, loc_a, *(++arg_ptr_data), MD5_T16, MD5_S14 );

   /*
   loc_a = md5_FF( loc_a, loc_b, loc_c, loc_d, arg_ptr_data[ 0], MD5_T01, MD5_S11 );
   loc_d = md5_FF( loc_d, loc_a, loc_b, loc_c, arg_ptr_data[ 1], MD5_T02, MD5_S12 );
   loc_c = md5_FF( loc_c, loc_d, loc_a, loc_b, arg_ptr_data[ 2], MD5_T03, MD5_S13 );
   loc_b = md5_FF( loc_b, loc_c, loc_d, loc_a, arg_ptr_data[ 3], MD5_T04, MD5_S14 );

   loc_a = md5_FF( loc_a, loc_b, loc_c, loc_d, arg_ptr_data[ 4], MD5_T05, MD5_S11 );
   loc_d = md5_FF( loc_d, loc_a, loc_b, loc_c, arg_ptr_data[ 5], MD5_T06, MD5_S12 );
   loc_c = md5_FF( loc_c, loc_d, loc_a, loc_b, arg_ptr_data[ 6], MD5_T07, MD5_S13 );
   loc_b = md5_FF( loc_b, loc_c, loc_d, loc_a, arg_ptr_data[ 7], MD5_T08, MD5_S14 );

   loc_a = md5_FF( loc_a, loc_b, loc_c, loc_d, arg_ptr_data[ 8], MD5_T09, MD5_S11 );
   loc_d = md5_FF( loc_d, loc_a, loc_b, loc_c, arg_ptr_data[ 9], MD5_T10, MD5_S12 );
   loc_c = md5_FF( loc_c, loc_d, loc_a, loc_b, arg_ptr_data[10], MD5_T11, MD5_S13 );
   loc_b = md5_FF( loc_b, loc_c, loc_d, loc_a, arg_ptr_data[11], MD5_T12, MD5_S14 );

   loc_a = md5_FF( loc_a, loc_b, loc_c, loc_d, arg_ptr_data[12], MD5_T13, MD5_S11 );
   loc_d = md5_FF( loc_d, loc_a, loc_b, loc_c, arg_ptr_data[13], MD5_T14, MD5_S12 );
   loc_c = md5_FF( loc_c, loc_d, loc_a, loc_b, arg_ptr_data[14], MD5_T15, MD5_S13 );
   loc_b = md5_FF( loc_b, loc_c, loc_d, loc_a, arg_ptr_data[15], MD5_T16, MD5_S14 );
   */

   /* 2nd round */
   loc_a = md5_GG( loc_a, loc_b, loc_c, loc_d, *(arg_ptr_data - 14U), MD5_T17, MD5_S21 );
   loc_d = md5_GG( loc_d, loc_a, loc_b, loc_c, *(arg_ptr_data -  9U), MD5_T18, MD5_S22 );
   loc_c = md5_GG( loc_c, loc_d, loc_a, loc_b, *(arg_ptr_data -  4U), MD5_T19, MD5_S23 );
   loc_b = md5_GG( loc_b, loc_c, loc_d, loc_a, *(arg_ptr_data - 15U), MD5_T20, MD5_S24 );

   loc_a = md5_GG( loc_a, loc_b, loc_c, loc_d, *(arg_ptr_data - 10U), MD5_T21, MD5_S21 );
   loc_d = md5_GG( loc_d, loc_a, loc_b, loc_c, *(arg_ptr_data -  5U), MD5_T22, MD5_S22 );
   loc_c = md5_GG( loc_c, loc_d, loc_a, loc_b, * arg_ptr_data       , MD5_T23, MD5_S23 );
   loc_b = md5_GG( loc_b, loc_c, loc_d, loc_a, *(arg_ptr_data - 11U), MD5_T24, MD5_S24 );

   loc_a = md5_GG( loc_a, loc_b, loc_c, loc_d, *(arg_ptr_data -  6U), MD5_T25, MD5_S21 );
   loc_d = md5_GG( loc_d, loc_a, loc_b, loc_c, *(arg_ptr_data -  1U), MD5_T26, MD5_S22 );
   loc_c = md5_GG( loc_c, loc_d, loc_a, loc_b, *(arg_ptr_data - 12U), MD5_T27, MD5_S23 );
   loc_b = md5_GG( loc_b, loc_c, loc_d, loc_a, *(arg_ptr_data -  7U), MD5_T28, MD5_S24 );

   loc_a = md5_GG( loc_a, loc_b, loc_c, loc_d, *(arg_ptr_data -  2U), MD5_T29, MD5_S21 );
   loc_d = md5_GG( loc_d, loc_a, loc_b, loc_c, *(arg_ptr_data - 13U), MD5_T30, MD5_S22 );
   loc_c = md5_GG( loc_c, loc_d, loc_a, loc_b, *(arg_ptr_data -  8U), MD5_T31, MD5_S23 );
   loc_b = md5_GG( loc_b, loc_c, loc_d, loc_a, *(arg_ptr_data -  3U), MD5_T32, MD5_S24 );

   /*
   loc_a = md5_GG( loc_a, loc_b, loc_c, loc_d, arg_ptr_data[ 1], MD5_T17, MD5_S21 );
   loc_d = md5_GG( loc_d, loc_a, loc_b, loc_c, arg_ptr_data[ 6], MD5_T18, MD5_S22 );
   loc_c = md5_GG( loc_c, loc_d, loc_a, loc_b, arg_ptr_data[11], MD5_T19, MD5_S23 );
   loc_b = md5_GG( loc_b, loc_c, loc_d, loc_a, arg_ptr_data[ 0], MD5_T20, MD5_S24 );

   loc_a = md5_GG( loc_a, loc_b, loc_c, loc_d, arg_ptr_data[ 5], MD5_T21, MD5_S21 );
   loc_d = md5_GG( loc_d, loc_a, loc_b, loc_c, arg_ptr_data[10], MD5_T22, MD5_S22 );
   loc_c = md5_GG( loc_c, loc_d, loc_a, loc_b, arg_ptr_data[15], MD5_T23, MD5_S23 );
   loc_b = md5_GG( loc_b, loc_c, loc_d, loc_a, arg_ptr_data[ 4], MD5_T24, MD5_S24 );

   loc_a = md5_GG( loc_a, loc_b, loc_c, loc_d, arg_ptr_data[ 9], MD5_T25, MD5_S21 );
   loc_d = md5_GG( loc_d, loc_a, loc_b, loc_c, arg_ptr_data[14], MD5_T26, MD5_S22 );
   loc_c = md5_GG( loc_c, loc_d, loc_a, loc_b, arg_ptr_data[ 3], MD5_T27, MD5_S23 );
   loc_b = md5_GG( loc_b, loc_c, loc_d, loc_a, arg_ptr_data[ 8], MD5_T28, MD5_S24 );

   loc_a = md5_GG( loc_a, loc_b, loc_c, loc_d, arg_ptr_data[13], MD5_T29, MD5_S21 );
   loc_d = md5_GG( loc_d, loc_a, loc_b, loc_c, arg_ptr_data[ 2], MD5_T30, MD5_S22 );
   loc_c = md5_GG( loc_c, loc_d, loc_a, loc_b, arg_ptr_data[ 7], MD5_T31, MD5_S23 );
   loc_b = md5_GG( loc_b, loc_c, loc_d, loc_a, arg_ptr_data[12], MD5_T32, MD5_S24 );
   */

   /* 3rd round */
   loc_a = md5_HH( loc_a, loc_b, loc_c, loc_d, *(arg_ptr_data - 10U), MD5_T33, MD5_S31 );
   loc_d = md5_HH( loc_d, loc_a, loc_b, loc_c, *(arg_ptr_data -  7U), MD5_T34, MD5_S32 );
   loc_c = md5_HH( loc_c, loc_d, loc_a, loc_b, *(arg_ptr_data -  4U), MD5_T35, MD5_S33 );
   loc_b = md5_HH( loc_b, loc_c, loc_d, loc_a, *(arg_ptr_data -  1U), MD5_T36, MD5_S34 );

   loc_a = md5_HH( loc_a, loc_b, loc_c, loc_d, *(arg_ptr_data - 14U), MD5_T37, MD5_S31 );
   loc_d = md5_HH( loc_d, loc_a, loc_b, loc_c, *(arg_ptr_data - 11U), MD5_T38, MD5_S32 );
   loc_c = md5_HH( loc_c, loc_d, loc_a, loc_b, *(arg_ptr_data -  8U), MD5_T39, MD5_S33 );
   loc_b = md5_HH( loc_b, loc_c, loc_d, loc_a, *(arg_ptr_data -  5U), MD5_T40, MD5_S34 );

   loc_a = md5_HH( loc_a, loc_b, loc_c, loc_d, *(arg_ptr_data -  2U), MD5_T41, MD5_S31 );
   loc_d = md5_HH( loc_d, loc_a, loc_b, loc_c, *(arg_ptr_data - 15U), MD5_T42, MD5_S32 );
   loc_c = md5_HH( loc_c, loc_d, loc_a, loc_b, *(arg_ptr_data - 12U), MD5_T43, MD5_S33 );
   loc_b = md5_HH( loc_b, loc_c, loc_d, loc_a, *(arg_ptr_data -  9U), MD5_T44, MD5_S34 );

   loc_a = md5_HH( loc_a, loc_b, loc_c, loc_d, *(arg_ptr_data -  6U), MD5_T45, MD5_S31 );
   loc_d = md5_HH( loc_d, loc_a, loc_b, loc_c, *(arg_ptr_data -  3U), MD5_T46, MD5_S32 );
   loc_c = md5_HH( loc_c, loc_d, loc_a, loc_b, * arg_ptr_data       , MD5_T47, MD5_S33 );
   loc_b = md5_HH( loc_b, loc_c, loc_d, loc_a, *(arg_ptr_data - 13U), MD5_T48, MD5_S34 );

   /*
   loc_a = md5_HH( loc_a, loc_b, loc_c, loc_d, arg_ptr_data[ 5], MD5_T33, MD5_S31 );
   loc_d = md5_HH( loc_d, loc_a, loc_b, loc_c, arg_ptr_data[ 8], MD5_T34, MD5_S32 );
   loc_c = md5_HH( loc_c, loc_d, loc_a, loc_b, arg_ptr_data[11], MD5_T35, MD5_S33 );
   loc_b = md5_HH( loc_b, loc_c, loc_d, loc_a, arg_ptr_data[14], MD5_T36, MD5_S34 );

   loc_a = md5_HH( loc_a, loc_b, loc_c, loc_d, arg_ptr_data[ 1], MD5_T37, MD5_S31 );
   loc_d = md5_HH( loc_d, loc_a, loc_b, loc_c, arg_ptr_data[ 4], MD5_T38, MD5_S32 );
   loc_c = md5_HH( loc_c, loc_d, loc_a, loc_b, arg_ptr_data[ 7], MD5_T39, MD5_S33 );
   loc_b = md5_HH( loc_b, loc_c, loc_d, loc_a, arg_ptr_data[10], MD5_T40, MD5_S34 );

   loc_a = md5_HH( loc_a, loc_b, loc_c, loc_d, arg_ptr_data[13], MD5_T41, MD5_S31 );
   loc_d = md5_HH( loc_d, loc_a, loc_b, loc_c, arg_ptr_data[ 0], MD5_T42, MD5_S32 );
   loc_c = md5_HH( loc_c, loc_d, loc_a, loc_b, arg_ptr_data[ 3], MD5_T43, MD5_S33 );
   loc_b = md5_HH( loc_b, loc_c, loc_d, loc_a, arg_ptr_data[ 6], MD5_T44, MD5_S34 );

   loc_a = md5_HH( loc_a, loc_b, loc_c, loc_d, arg_ptr_data[ 9], MD5_T45, MD5_S31 );
   loc_d = md5_HH( loc_d, loc_a, loc_b, loc_c, arg_ptr_data[12], MD5_T46, MD5_S32 );
   loc_c = md5_HH( loc_c, loc_d, loc_a, loc_b, arg_ptr_data[15], MD5_T47, MD5_S33 );
   loc_b = md5_HH( loc_b, loc_c, loc_d, loc_a, arg_ptr_data[ 2], MD5_T48, MD5_S34 );
   */

   /* 4th round */
   loc_a = md5_II( loc_a, loc_b, loc_c, loc_d, *(arg_ptr_data - 15U), MD5_T49, MD5_S41 );
   loc_d = md5_II( loc_d, loc_a, loc_b, loc_c, *(arg_ptr_data -  8U), MD5_T50, MD5_S42 );
   loc_c = md5_II( loc_c, loc_d, loc_a, loc_b, *(arg_ptr_data -  1U), MD5_T51, MD5_S43 );
   loc_b = md5_II( loc_b, loc_c, loc_d, loc_a, *(arg_ptr_data - 10U), MD5_T52, MD5_S44 );

   loc_a = md5_II( loc_a, loc_b, loc_c, loc_d, *(arg_ptr_data -  3U), MD5_T53, MD5_S41 );
   loc_d = md5_II( loc_d, loc_a, loc_b, loc_c, *(arg_ptr_data - 12U), MD5_T54, MD5_S42 );
   loc_c = md5_II( loc_c, loc_d, loc_a, loc_b, *(arg_ptr_data -  5U), MD5_T55, MD5_S43 );
   loc_b = md5_II( loc_b, loc_c, loc_d, loc_a, *(arg_ptr_data - 14U), MD5_T56, MD5_S44 );

   loc_a = md5_II( loc_a, loc_b, loc_c, loc_d, *(arg_ptr_data -  7U), MD5_T57, MD5_S41 );
   loc_d = md5_II( loc_d, loc_a, loc_b, loc_c, * arg_ptr_data       , MD5_T58, MD5_S42 );
   loc_c = md5_II( loc_c, loc_d, loc_a, loc_b, *(arg_ptr_data -  9U), MD5_T59, MD5_S43 );
   loc_b = md5_II( loc_b, loc_c, loc_d, loc_a, *(arg_ptr_data -  2U), MD5_T60, MD5_S44 );

   loc_a = md5_II( loc_a, loc_b, loc_c, loc_d, *(arg_ptr_data - 11U), MD5_T61, MD5_S41 );
   loc_d = md5_II( loc_d, loc_a, loc_b, loc_c, *(arg_ptr_data -  4U), MD5_T62, MD5_S42 );
   loc_c = md5_II( loc_c, loc_d, loc_a, loc_b, *(arg_ptr_data - 13U), MD5_T63, MD5_S43 );
   loc_b = md5_II( loc_b, loc_c, loc_d, loc_a, *(arg_ptr_data -  6U), MD5_T64, MD5_S44 );

   /*
   loc_a = md5_II( loc_a, loc_b, loc_c, loc_d, arg_ptr_data[ 0], MD5_T49, MD5_S41 );
   loc_d = md5_II( loc_d, loc_a, loc_b, loc_c, arg_ptr_data[ 7], MD5_T50, MD5_S42 );
   loc_c = md5_II( loc_c, loc_d, loc_a, loc_b, arg_ptr_data[14], MD5_T51, MD5_S43 );
   loc_b = md5_II( loc_b, loc_c, loc_d, loc_a, arg_ptr_data[ 5], MD5_T52, MD5_S44 );

   loc_a = md5_II( loc_a, loc_b, loc_c, loc_d, arg_ptr_data[12], MD5_T53, MD5_S41 );
   loc_d = md5_II( loc_d, loc_a, loc_b, loc_c, arg_ptr_data[ 3], MD5_T54, MD5_S42 );
   loc_c = md5_II( loc_c, loc_d, loc_a, loc_b, arg_ptr_data[10], MD5_T55, MD5_S43 );
   loc_b = md5_II( loc_b, loc_c, loc_d, loc_a, arg_ptr_data[ 1], MD5_T56, MD5_S44 );

   loc_a = md5_II( loc_a, loc_b, loc_c, loc_d, arg_ptr_data[ 8], MD5_T57, MD5_S41 );
   loc_d = md5_II( loc_d, loc_a, loc_b, loc_c, arg_ptr_data[15], MD5_T58, MD5_S42 );
   loc_c = md5_II( loc_c, loc_d, loc_a, loc_b, arg_ptr_data[ 6], MD5_T59, MD5_S43 );
   loc_b = md5_II( loc_b, loc_c, loc_d, loc_a, arg_ptr_data[13], MD5_T60, MD5_S44 );

   loc_a = md5_II( loc_a, loc_b, loc_c, loc_d, arg_ptr_data[ 4], MD5_T61, MD5_S41 );
   loc_d = md5_II( loc_d, loc_a, loc_b, loc_c, arg_ptr_data[11], MD5_T62, MD5_S42 );
   loc_c = md5_II( loc_c, loc_d, loc_a, loc_b, arg_ptr_data[ 2], MD5_T63, MD5_S43 );
   loc_b = md5_II( loc_b, loc_c, loc_d, loc_a, arg_ptr_data[ 9], MD5_T64, MD5_S44 );
   */

   *   arg_ptr_hash  += loc_d;
   *(--arg_ptr_hash) += loc_c;
   *(--arg_ptr_hash) += loc_b;
   *(--arg_ptr_hash) += loc_a;
}

/*================================================[ public functions ]================================================*/

RC md5_initialize(sMD5_state_t * const arg_ptr_state )
{
   if ( arg_ptr_state != NULL )
   {
      (void)memset( arg_ptr_state->bData, 0x00U, MD5_MSG_PACKET_SIZE );
      arg_ptr_state->iDatasize[0U] = 0U;
      arg_ptr_state->iDatasize[1U] = 0U;
      arg_ptr_state->bHash[0U] = MD5_HASH0_INIT;
      arg_ptr_state->bHash[1U] = MD5_HASH1_INIT;
      arg_ptr_state->bHash[2U] = MD5_HASH2_INIT;
      arg_ptr_state->bHash[3U] = MD5_HASH3_INIT;
      return RC_SUCCESS;
   }
   else
   {
      return RC_ERROR_NULL;
   }
}

RC md5_set_start_hash(sMD5_state_t * const arg_ptr_state
                     ,const U32 arg_hash0_init
                     ,const U32 arg_hash1_init
                     ,const U32 arg_hash2_init
                     ,const U32 arg_hash3_init
                     )
{
   if ( NULL != arg_ptr_state )
   {
      (void)memset( arg_ptr_state->bData, 0x00U, MD5_MSG_PACKET_SIZE );
      arg_ptr_state->iDatasize[0U] = 0U;
      arg_ptr_state->iDatasize[1U] = 0U;
      arg_ptr_state->bHash[0U] = arg_hash0_init;
      arg_ptr_state->bHash[1U] = arg_hash1_init;
      arg_ptr_state->bHash[2U] = arg_hash2_init;
      arg_ptr_state->bHash[3U] = arg_hash3_init;
      return RC_SUCCESS;
   }
   else
   {
      return RC_ERROR_NULL;
   }
}

RC md5_update(sMD5_state_t * const arg_ptr_state
             ,const U8 * arg_ptr_data
             ,const U16 arg_datasize )
{
   if (   ( NULL != arg_ptr_state )
       && ( NULL != arg_ptr_data )
      )
   {
      /* determine size of data that are still in buffer */                                        
      const U8 loc_bytes_left_last_update = (U8)(BIN_CONV_BITS_TO_BYTES(arg_ptr_state->iDatasize[0U]) & 63UL); /* max 63 Bytes */

      /* update datasize */
      const U32 loc_bits_new_data = BIN_CONV_BYTES_TO_BITS(arg_datasize);
      /* arg_ptr_state->iDatasize[1] += (U32)arg_datasize >> 29U; */ /* CONV32_BYTES_TO_BITS((U32)arg_datasize) >> 32U */
      /* adds always 0 as long as arg_datasize is a 16 bit argument */
      arg_ptr_state->iDatasize[0U] += loc_bits_new_data;
      if ( arg_ptr_state->iDatasize[0U] < loc_bits_new_data ) /* overflow case -> increment high order word */
      {
         arg_ptr_state->iDatasize[1U]++;
      }
      {
         /* determine padding bytes */
         const U8 loc_padding_bytes = MD5_MSG_PACKET_SIZE - loc_bytes_left_last_update; /* 64 - [0..63] = [1..64]*/
         if ( arg_datasize >= loc_padding_bytes ) /* enough data to complete at least one message packet */
         {
            /* hash full packets */
            const U16 loc_iNoOfFullPackets = (U16)( (arg_datasize - (U16)loc_padding_bytes) >> 0x06U );
                                             /* ( arg_datasize / MD5_MSG_PACKET_SIZE ) */
            /* prepare buffer for first packet */
            (void)memcpy( ((U8 *)arg_ptr_state->bData + loc_bytes_left_last_update), arg_ptr_data, loc_padding_bytes );
            md5_calc_packet_hash( arg_ptr_state->bData, arg_ptr_state->bHash );
            arg_ptr_data += loc_padding_bytes; /* sets  pointer to next data */
            {
               U16 loc_iCurrentPacket = 0U;
               for ( loc_iCurrentPacket = 0U; loc_iCurrentPacket < loc_iNoOfFullPackets; loc_iCurrentPacket++ )
               {
                  md5_calc_packet_hash( (U32 *)arg_ptr_data, arg_ptr_state->bHash );
                  arg_ptr_data += MD5_MSG_PACKET_SIZE; /* Sets data pointer to the next packet. */
               }
            }
            {
               /* determine left data and prepare buffer for next update */
               const U16 loc_new_data_left = ( arg_datasize - (U16)loc_padding_bytes ) % MD5_MSG_PACKET_SIZE ;
               if ( 0U < loc_new_data_left )
               {
                 (void)memcpy( arg_ptr_state->bData, arg_ptr_data, loc_new_data_left );
               }
            }
         }
         else
         {
            /* append new data */
            (void)memcpy( ((U8 *)arg_ptr_state->bData + loc_bytes_left_last_update), arg_ptr_data, arg_datasize );
         }
      }
      return RC_SUCCESS;
   }
   else
   {
     return RC_ERROR_NULL;
   }
}

RC md5_finalize(sMD5_state_t * const arg_ptr_state
               , const U16 arg_datasize_offset
               )
{
   if ( NULL != arg_ptr_state )
   {
      /* update datasize */
      const U32 loc_bits_offset_data = BIN_CONV_BYTES_TO_BITS(arg_datasize_offset);
      arg_ptr_state->iDatasize[0U] += loc_bits_offset_data;
      arg_ptr_state->iDatasize[1U] += ((U32)arg_datasize_offset >> 29U);
      /* adds always 0 as long as arg_datasize_offset is a 16 bit argument */
      if ( arg_ptr_state->iDatasize[0U] < loc_bits_offset_data ) /* overflow case -> increment high order word */
      {
         arg_ptr_state->iDatasize[1U]++;
      }
      {
         /* save datasize before padding. little endian only */ /* TODO: big endian */
         U32 loc_final_datasize[2U] = { 0U, 0U };
         loc_final_datasize[0U] = arg_ptr_state->iDatasize[0U];
         loc_final_datasize[1U] = arg_ptr_state->iDatasize[1U];

         /* do the message padding */
         U8 loc_iNoOfBytesToPad = (U8)((MD5_MAX_SIZE_LAST_MSG_PACKET - BIN_CONV_BITS_TO_BYTES(arg_ptr_state->iDatasize[0U])) & 63UL) + 1U;
            /* tricky calculation of needed padding bytes */
         if ( RC_SUCCESS == md5_update( arg_ptr_state, CRY_MSG_PADDING, loc_iNoOfBytesToPad ) )
         {
            /* append datasize and finish hash calculation */
            if ( RC_SUCCESS == md5_update( arg_ptr_state, (U8 *)loc_final_datasize, 8U ) )
            {
               return RC_SUCCESS;
            }
            else
            {
              return RC_ERROR;
            }
         }
         else
         {
           return RC_ERROR;
         }
      }
   }
   else
   {
     return RC_ERROR_NULL;
   }
}

/***********************************************************************************************************************
*                             Copyright 2011 Linde Material Handling. All rights reserved.                             *
***********************************************************************************************************************/
